home *** CD-ROM | disk | FTP | other *** search
- Path: xanth!mcnc!gatech!cwjcc!hal!ncoast!allbery
- From: ok@quintus.UUCP
- Newsgroups: comp.sources.misc
- Subject: v04i013: Replacement for strtod()
- Message-ID: <232@quintus.UUCP>
- Date: 3 Aug 88 01:19:11 GMT
- Sender: allbery@ncoast.UUCP
- Reply-To: ok@quintus.UUCP
- Organization: Quintus Computer Systems, Inc.
- Lines: 218
- Approved: allbery@ncoast.UUCP
-
- Posting-number: Volume 4, Issue 13
- Submitted-by: "A. Nonymous" <ok@quintus.UUCP>
- Archive-name: strtod
-
- strtod() is a nice function, but it's often missing or broken.
- Here is a replacement for it which does the parsing and checking
- required of strtod() but calls atof() to do the conversion.
-
- ---- cut here ---- cut here ---- cut here ---- cut here ---- cut here ----
- #!/bin/sh
- # This is a shell archive, meaning:
- # 1. Remove everything above the #!/bin/sh line.
- # 2. Save the resulting test in a file.
- # 3. Execute the file with /bin/sh (not csh) to create the files:
- # README
- # makefile
- # str2dbl.c
- sed -e 's/^X//' >README <<'------ EOF ------'
- XFILES:
- X README - this file
- X makefile - a trivial makefile
- X str2dbl.c - defines str2dbl(), requires atof() + float support
- X
- Xdouble str2dbl(char *str, char **ptr)
- X converts the character string str points to to a double-precision
- X floating-point number and returns it. str2dbl() recognises
- X
- X <space>... [+|-] <digit>... [. <digit>...] [<exponent>]
- X
- X where <exponent> is
- X
- X e|E [+|-| ] <digit> <digit>...
- X
- X If ptr is not (char**)NULL, *ptr is assigned a pointer to the
- X character just after the last character accepted by str2dbl().
- X {This will typically be a layout character or NUL.}
- X
- X If there aren't any digits at the front (e.g. the input is
- X "e45" or "Fred" or "-gotcha-") str2dbl() will make *ptr point
- X to the whole of str (even if there were leading spaces and signs)
- X and will return 0.0.
- X
- X If there is some problem with the exponent (e.g. the input is
- X "1.2e%45") str2dbl() will reject all of the exponent, making *ptr
- X point to the 'e', and will convert only the preceding characters.
- X
- X A single space after the 'e' or 'E' of an exponent is accepted as
- X if it were a '+' sign, because some output formatting routines
- X generate numbers that look like that. Spaces are not otherwise
- X accepted inside numbers.
- X
- X If the correct value cannot be represented, str2dbl() sets errno
- X to ERANGE, and returns +/-HUGE for overflow, +/-ZERO for underflow.
- X
- XWARNING:
- X The source code as provided is set up for 64-bit IEEE-754 floating
- X point. VAX and IBM floating-point formats are different, so the
- X numeric range is different. Some machines which look as though
- X they have IEEE floats may not support infinities or denormalised
- X numbers, in which case the numeric range is different. You will
- X have to determine the correct values for your machine.
- X
- ------ EOF ------
- ls -l README
- sed -e 's/^X//' >makefile <<'------ EOF ------'
- Xstr2dbl.o: str2dbl.c
- ------ EOF ------
- ls -l makefile
- sed -e 's/^X//' >str2dbl.c <<'------ EOF ------'
- X/* File : str2dbl.c
- X Author : Richard A. O'Keefe @ Quintus Computer Systems, Inc.
- X Updated: Tuesday August 2nd, 1988
- X Defines: double str2dbl(char *str, char**ptr)
- X*/
- X
- X/* This is an implementation of the strtod() function described in the
- X System V manuals, with a different name to avoid linker problems.
- X All that str2dbl() does itself is check that the argument is well-formed
- X and is in range. It leaves the work of conversion to atof(), which is
- X assumed to exist and deliver correct results (if they can be represented).
- X
- X There are two reasons why this should be provided to the net:
- X (a) some UNIX systems do not yet have strtod(), or do not have it
- X available in the BSD "universe" (but they do have atof()).
- X (b) some of the UNIX systems that *do* have it get it wrong.
- X (some crash with large arguments, some assign the wrong *ptr value).
- X There is a reason why *we* are providing it: we need a correct version
- X of strtod(), and if we give this one away maybe someone will look for
- X mistakes in it and fix them for us (:-).
- X*/
- X
- X/* The following constants are machine-specific. MD{MIN,MAX}EXPT are
- X integers and MD{MIN,MAX}FRAC are strings such that
- X 0.${MDMAXFRAC}e${MDMAXEXPT} is the largest representable double,
- X 0.${MDMINFRAC}e${MDMINEXPT} is the smallest representable +ve double
- X MD{MIN,MAX}FRAC must not have any trailing zeros.
- X The values here are for IEEE-754 64-bit floats.
- X It is not perfectly clear to me whether an IEEE infinity should be
- X returned for overflow, nor what a portable way of writing one is,
- X so HUGE is just 0.MAXFRAC*10**MAXEXPT (this seems still to be the
- X UNIX convention).
- X
- X I do know about <values.h>, but the whole point of this file is that
- X we can't always trust that stuff to be there or to be correct.
- X*/
- Xstatic int MDMINEXPT = {-323};
- Xstatic char MDMINFRAC[] = "494065645841246544";
- Xstatic double ZERO = 0.0;
- X
- Xstatic int MDMAXEXPT = { 309};
- Xstatic char MDMAXFRAC[] = "17976931348623147";
- Xstatic double HUGE = 1.7976931348623147e308;
- X
- Xextern double atof(); /* Only called when result known to be ok */
- X
- X#include <errno.h>
- Xextern int errno;
- X
- Xdouble str2dbl(str, ptr)
- X char *str;
- X char **ptr;
- X {
- X int sign, scale, dotseen;
- X int esign, expt;
- X char *save;
- X register char *sp, *dp;
- X register int c;
- X char *buforg, *buflim;
- X char buffer[64]; /* 45-digit significand + */
- X /* 13-digit exponent */
- X sp = str;
- X while (*sp == ' ') sp++;
- X sign = 1;
- X if (*sp == '-') sign -= 2, sp++;
- X dotseen = 0, scale = 0;
- X dp = buffer;
- X *dp++ = '0'; *dp++ = '.';
- X buforg = dp, buflim = buffer+48;
- X for (save = sp; c = *sp; sp++)
- X if (c == '.') {
- X if (dotseen) break;
- X dotseen++;
- X } else
- X if ((unsigned)(c-'0') > (unsigned)('9'-'0')) {
- X break;
- X } else
- X if (c == '0') {
- X if (dp != buforg) {
- X /* This is not the first digit, so we want to keep it */
- X if (dp < buflim) *dp++ = c;
- X } else {
- X /* No non-zero digits seen yet */
- X /* If a . has been seen, scale must be adjusted */
- X if (dotseen) scale--;
- X }
- X } else {
- X /* This is a nonzero digit, so we want to keep it */
- X if (dp < buflim) *dp++ = c;
- X /* If it precedes a ., scale must be adjusted */
- X if (!dotseen) scale++;
- X }
- X if (sp == save) {
- X if (ptr) *ptr = str;
- X errno = EDOM; /* what should this be? */
- X return ZERO;
- X }
- X
- X while (dp > buforg && dp[-1] == '0') --dp;
- X if (dp == buforg) *dp++ = '0';
- X *dp = '\0';
- X /* Now the contents of buffer are
- X +--+--------+-+--------+
- X |0.|fraction|\|leftover|
- X +--+--------+-+--------+
- X ^dp points here
- X where fraction begins with 0 iff it is "0", and has at most
- X 45 digits in it, and leftover is at least 16 characters.
- X */
- X save = sp, expt = 0, esign = 1;
- X do {
- X c = *sp++;
- X if (c != 'e' && c != 'E') break;
- X c = *sp++;
- X if (c == '-') esign -= 2, c = *sp++; else
- X if (c == '+' || c == ' ') c = *sp++;
- X if ((unsigned)(c-'0') > (unsigned)('9'-'0')) break;
- X while (c == '0') c = *sp++;
- X for (; (unsigned)(c-'0') <= (unsigned)('9'-'0'); c = *sp++)
- X expt = expt*10 + c-'0';
- X if (esign < 0) expt = -expt;
- X save = sp-1;
- X } while (0);
- X if (ptr) *ptr = save;
- X expt += scale;
- X /* Now the number is sign*0.fraction*10**expt */
- X errno = ERANGE;
- X if (expt > MDMAXEXPT) {
- X return HUGE*sign;
- X } else
- X if (expt == MDMAXEXPT) {
- X if (strcmp(buforg, MDMAXFRAC) > 0) return HUGE*sign;
- X } else
- X if (expt < MDMINEXPT) {
- X return ZERO*sign;
- X } else
- X if (expt == MDMINEXPT) {
- X if (strcmp(buforg, MDMINFRAC) < 0) return ZERO*sign;
- X }
- X /* We have now established that the number can be */
- X /* represented without overflow or underflow */
- X (void) sprintf(dp, "E%d", expt);
- X errno = 0;
- X return atof(buffer)*sign;
- X }
- X
- ------ EOF ------
- ls -l str2dbl.c
- exit 0
-